//
//  MFWDownloadManager.m
//  download
//
//  Created by 陈曦 on 13-4-24.
//  Copyright (c) 2013年 陈曦. All rights reserved.
//

#import "MFWDownloadManager.h"
#import "NSString+SystemPath.h"
#import <SystemConfiguration/SystemConfiguration.h>
#import <MobileCoreServices/MobileCoreServices.h>
#import "MFWDownloadTask.h"
#import "NSString+SystemPath.h"
#import "sys/xattr.h"
#import "FMDatabase.h"
#import "MFWDownloadDBHelp.h"
#import "MFWDownloadDBHelp+FixAbsolutePath.h"

#ifndef MFWSdkHeaders
    #import "ASIHTTPRequest.h"
#endif


NSString *MFW_DOWNLOAD_NOTIFY_INDEX_KEY       = @"mfw_download_notify_index_key";
NSString *MFW_DOWNLOAD_PERCENT_CHANGE_NOTIFY  = @"mfw_download_percent_change_notify";
NSString *MFW_DOWNLOAD_STATE_CHANGE_NOTIFY    = @"mfw_download_state_change_notify";

#define TASK_OBJ_KEY                            @"task_obj_key"
#define TASK_FILE_NAME                          @"tasks.plist"

#pragma mark - 
#pragma mark item




#pragma mark -
#pragma mark manager

@interface MFWDownloadManager()<MFWDownloadTaskDelegate>

//@property (nonatomic, retain)   NSMutableArray  *tasks;
@property (nonatomic, readonly) NSString            *cachePath;
@property (nonatomic, copy)     NSString            *_cachePath;

@property (nonatomic, retain)   NSMutableDictionary *itemDict;
@property (nonatomic, retain)   NSMutableArray      *itemAll;

@property (nonatomic, retain)   NSMutableArray      *itemStateGroups;

@property (nonatomic, readonly) NSArray             *itemStop;
@property (nonatomic, readonly) NSArray             *itemRunning;
@property (nonatomic, readonly) NSArray             *itemError;
@property (nonatomic, readonly) NSArray             *itemPause;
@property (nonatomic, readonly) NSArray             *itemWaiting;
@property (nonatomic, readonly) NSArray             *itemFinish;

@property (nonatomic, retain)   NSTimer             *timer;
@property (nonatomic, copy)     NSString            *catalogName;

@property (nonatomic, readonly) NSUInteger          curConcurrentDownloadCount;
@property (nonatomic, readonly) BOOL                isMaxConcurrentDownload;

@property (nonatomic, retain)   MFWDownloadDBHelp     *db;

@end

@implementation MFWDownloadManager


- (NSArray*)itemStop    { return [self.itemStateGroups objectAtIndex:MFWDownloadTaskStateStop    ]; }
- (NSArray*)itemRunning { return [self.itemStateGroups objectAtIndex:MFWDownloadTaskStateRunning ]; }
- (NSArray*)itemError   { return [self.itemStateGroups objectAtIndex:MFWDownloadTaskStateError   ]; }
- (NSArray*)itemPause   { return [self.itemStateGroups objectAtIndex:MFWDownloadTaskStatePause   ]; }
- (NSArray*)itemWaiting { return [self.itemStateGroups objectAtIndex:MFWDownloadTaskStateWaiting ]; }
- (NSArray*)itemFinish  { return [self.itemStateGroups objectAtIndex:MFWDownloadTaskStateFinished]; }

- (NSUInteger)curConcurrentDownloadCount
{
    return self.itemRunning.count;
}

- (BOOL)isMaxConcurrentDownload
{
    return self.itemRunning.count < self.MaxConcurrentDownloadCount;
}

- (void)setMaxConcurrentDownloadCount:(NSUInteger)MaxConcurrentDownloadCount
{
    _MaxConcurrentDownloadCount = MaxConcurrentDownloadCount;
    while (self.itemRunning.count > MaxConcurrentDownloadCount) {
        [self setTask:self.itemRunning.lastObject state:MFWDownloadTaskStateWaiting];
    }
}

- (void)dealloc
{
    [self stopTimer];
}

- (void)_postPercent
{
    if (self.itemRunning.count == 0)
    {
        NSLog(@"error function postPercent self.itemRunning.count can not be 0 ");
        [self stopTimer];
    }
    for (MFWDownloadTask *task in self.itemRunning)
    {
        NSUInteger index = [self.itemAll indexOfObject:task];
        NSNumber *indexNumber = [[NSNumber alloc] initWithUnsignedInteger:index];
        NSDictionary *dict = [[NSDictionary alloc] initWithObjectsAndKeys:indexNumber,
                              MFW_DOWNLOAD_NOTIFY_INDEX_KEY,
                              nil];
        
        [[NSNotificationCenter defaultCenter] postNotificationName:MFW_DOWNLOAD_PERCENT_CHANGE_NOTIFY
                                                            object:self
                                                          userInfo:dict];
    }
}

- (void)postPercent
{
    [self performSelectorOnMainThread:@selector(_postPercent) withObject:nil waitUntilDone:NO];
}

- (void)startTimer
{
    if (!self.timer)
    {
        self.timer = [NSTimer scheduledTimerWithTimeInterval:0.3
                                                      target:self
                                                    selector:@selector(postPercent)
                                                    userInfo:nil
                                                     repeats:YES];
    }
}

- (void)stopTimer
{
    if (self.timer)
    {
        [self.timer invalidate];
        self.timer = nil;
    }
}

- (MFWDownloadState)getStateByIndex:(NSUInteger)aIndex
{
    return [self getTaskByIndex:aIndex].item.state;
}
- (NSDictionary*)getUserInfoByIndex:(NSUInteger)aIndex
{
    return [self getTaskByIndex:aIndex].item.userInfo;
}
- (NSString*)getUrlByIndex:(NSUInteger)aIndex
{
    return [self getTaskByIndex:aIndex].item.url;
}
- (NSString*)getLocalPathByIndex:(NSUInteger)aIndex
{
    return [self getTaskByIndex:aIndex].item.localPath;
}

- (void)_postStateChange:(NSDictionary*)aUserInfo
{
    [[NSNotificationCenter defaultCenter] postNotificationName:MFW_DOWNLOAD_STATE_CHANGE_NOTIFY
                                                        object:self
                                                      userInfo:aUserInfo];
}

- (void)postStateChange:(NSDictionary*)aUserInfo
{
    [self performSelectorOnMainThread:@selector(_postStateChange:) withObject:aUserInfo waitUntilDone:NO];
}

- (void)setTask:(MFWDownloadTask*)aTask
          state:(MFWDownloadState)aState
{
    if (!aTask)
    {
        return;
    }
    short index = 0;
    NSUInteger aStop    =  1 << index++;
    NSUInteger aStart   =  1 << index++;
    
    NSUInteger noinit   = MFWDownloadTaskStateNotInit  << index;
    NSUInteger stop     = MFWDownloadTaskStateStop     << index;
    NSUInteger run      = MFWDownloadTaskStateRunning  << index;
    NSUInteger err      = MFWDownloadTaskStateError    << index;
    NSUInteger pause    = MFWDownloadTaskStatePause    << index;
    NSUInteger wait     = MFWDownloadTaskStateWaiting  << index;
    NSUInteger finish   = MFWDownloadTaskStateFinished << index;
    
    NSUInteger actions[MFWDownloadTaskStateCount * MFWDownloadTaskStateCount] = {
//newState   NotInit  Stop         Running      Error      Pause        Waiting   Finished
// oldState
/* NotInit */noinit , stop       , run|aStart , err      , pause       , wait       , finish,
/* Stop    */stop   , stop       , run|aStart , err      , stop        , wait       , finish,
/* Running */run    , stop|aStop , run        , err|stop , pause|aStop , wait|aStop , finish,
/* Error   */err    , stop       , run|aStart , err      , err         , wait       , finish,
/* Pause   */pause  , stop       , run|aStart , err      , pause       , wait       , finish,
/* Waiting */wait   , stop       , run|aStart , err      , wait        , wait       , finish,
/* Finished*/finish , finish     , finish     , finish   , finish      , finish     , finish
    };
    
    NSUInteger action = actions[aTask.item.state * MFWDownloadTaskStateCount + aState];
    
    if (aStop & action)
    {
        [aTask stop];
    }
    if (aStart & action)
    {
        [aTask start];
    }
    
    MFWDownloadState newState = action >> index;
    MFWDownloadState oldState = aTask.item.state;
#if DEBUG
//    NSArray *namearr = 
//@[ @"MFWDownloadTaskStateNotInit",
// @"MFWDownloadTaskStateStop",
// @"MFWDownloadTaskStateRunning",
// @"MFWDownloadTaskStateError",
// @"MFWDownloadTaskStatePause",
// @"MFWDownloadTaskStateWaiting",
// @"MFWDownloadTaskStateFinished"];
//    NSLog(@"%@ to %@",namearr[oldState],namearr[newState]);

#endif
    if(oldState == newState)
    {
        // do nothing
    }
    else
    {
        
        aTask.item.state = newState;
        NSMutableArray *arr = [self.itemStateGroups objectAtIndex:oldState];
        [arr removeObject:aTask];
        
        arr = [self.itemStateGroups objectAtIndex:newState];
        [arr addObject:aTask];

        // 如果不是从硬盘读取的 则发送通知
        if (oldState != MFWDownloadTaskStateNotInit)
        {
            NSUInteger index = [self.itemAll indexOfObject:aTask];
            NSNumber *number = [NSNumber numberWithUnsignedInteger:index];
            NSDictionary *dict = [[NSDictionary alloc] initWithObjectsAndKeys:number,MFW_DOWNLOAD_NOTIFY_INDEX_KEY, nil];
            [self postStateChange:dict];
        }
        
        // 如果下载任务中断则开启等待队伍
        if (oldState == MFWDownloadTaskStateRunning)
        {
            [self startWaitingItem];
        }
        
        if (self.itemRunning.count)
        {
            [self startTimer];
        }
        else
        {
            [self stopTimer];
        }
    }
    [self.db updateState:newState withID:aTask.item.uuid];
}

- (NSString*)cachePath
{
    if (!self._cachePath)
    {
        self._cachePath = [NSString libraryPath:@"/DownloadCache"];
        if(![[NSFileManager defaultManager] fileExistsAtPath:self.cachePath])
        {
            [[NSFileManager defaultManager] createDirectoryAtPath:self._cachePath
                                      withIntermediateDirectories:YES
                                                       attributes:nil
                                                            error:nil];
        }
        [[NSURL fileURLWithPath:self._cachePath] setDoNotBackup];
    }
    return self._cachePath;
}

+ (instancetype)defaultManager
{
    static MFWDownloadManager *manager = nil;
    if (!manager)
    {
        manager = [[MFWDownloadManager alloc] initWithCatalogName:nil];
    }
    return manager;
}

+ (instancetype)managerNamedCatalog:(NSString *)aCatalogName
{
    if (aCatalogName.length == 0)
    {
        return [MFWDownloadManager defaultManager];
    }
    
    static NSMutableDictionary *dict = nil;
    if (!dict)
    {
        dict = [[NSMutableDictionary alloc] init];
    }
    
    MFWDownloadManager *manager = [dict valueForKey:aCatalogName];
    if (!manager)
    {
        manager = [[MFWDownloadManager alloc] initWithCatalogName:aCatalogName];
        [dict setValue:manager forKey:aCatalogName];
    }
    return manager;
}

#define SAVE_URL_KEY        @"url"
#define SAVE_PATH_KEY       @"path"
#define SAVE_TEMP_PATH_KEY  @"temppath"
#define SAVE_STATE_KEY      @"state"
#define SAVE_UUID_KEY       @"uuid"
#define SAVE_USER_INFO      @"userinfo"

- (NSString*)getPlist
{
    NSString *fileName = [NSString stringWithFormat:@"%@_%@",self.catalogName,TASK_FILE_NAME];
    return [self.cachePath stringByAppendingPathComponent:fileName];
}

- (void)loadTask
{
    [self.itemDict removeAllObjects];
    [self.itemAll removeAllObjects];
    NSArray *arr = [self.db getAllItems];
    for (MFWDownloadItem *item in arr)
    {
        if (item.state == MFWDownloadTaskStateRunning || item.state == MFWDownloadTaskStateWaiting)
        {
            item.state = MFWDownloadTaskStateStop;
        }
        [self addTaskWithItem:item];
    }
}

- (id)init
{
    self = [self initWithCatalogName:nil];
    if (self)
    {
        // no nothing
    }
    return self;
}
#define DEFAULT_CATALOG_NAME @"Default"

- (id)initWithCatalogName:(NSString*)aCatalogName
{
    self = [super init];
    if (self)
    {
        if ([aCatalogName.lowercaseString isEqualToString:DEFAULT_CATALOG_NAME.lowercaseString])
        {
            NSLog(@"error 不要重复使用此catalogname");
        }
        
        if (0 == aCatalogName.length)
        {
            aCatalogName = DEFAULT_CATALOG_NAME;
        }
        self.MaxConcurrentDownloadCount = 3;
        self.catalogName = aCatalogName;
        self.itemAll = [[NSMutableArray alloc] init];
        self.itemDict = [[NSMutableDictionary alloc] init];
        
        self.itemStateGroups = [[NSMutableArray alloc] init];
        for (NSInteger i = 0 ;i < MFWDownloadTaskStateCount ; i++)
        {
            NSMutableArray *arr = [[NSMutableArray alloc] init];
            [self.itemStateGroups addObject:arr];
        }
        
        NSString *fileName = [NSString stringWithFormat:@"%@_%@",self.catalogName,@"download.sqlite"];
        
        self.db = [[MFWDownloadDBHelp alloc] initWithPath:[self.cachePath stringByAppendingPathComponent:fileName]];
        [self.db fixAbsoluteFilePathFromAppVersion];
        
        [self loadTask];
    }
    return self;
}

// 从此以后调用不验证state正确性 请验证运行等待等状态
- (void)addTaskWithItem:(MFWDownloadItem*)aItem
{
    MFWDownloadTask *task = [[MFWDownloadTask alloc] initWithItem:aItem];
    task.delegate = self;
    
    [self.itemDict setValue:task forKey:aItem.uuid];
    [self.itemAll addObject:task];
    
    [self setTask:task state:aItem.state];
}

- (NSUInteger)startDownloadFile:(NSString *)aUrl
                  withLocalPath:(NSString *)aPath
{
    return [self startDownloadFile:aUrl
                     withLocalPath:aPath
                             andID:nil];
}

- (NSUInteger)startDownloadFile:(NSString *)aUrl
                 withLocalPath:(NSString *)aPath
                         andID:(NSString *)aID
{
    NSUInteger extendIndex = [self getIndexByUUID:aID];
    NSString *uuid = aID;
    NSUInteger index = 0;
    if (extendIndex < self.itemAll.count)
    {
        index = extendIndex;
        MFWDownloadTask *task = [self getTaskByIndex:extendIndex];
        task.item.url = aUrl;
        uuid = task.item.uuid;
        
        if (NO == [aPath isEqualToString:task.item.localPath])
        {
            [[NSFileManager defaultManager] moveItemAtPath:task.item.localPath toPath:aPath error:nil];
            task.item.localPath = aPath;
        }
        
        [self startTask:task];
    }
    else
    {
        // 下载数据
        if (!aID)
        {
            CFUUIDRef theUUID = CFUUIDCreate(NULL);
            CFStringRef string = CFUUIDCreateString(NULL, theUUID);
            CFRelease(theUUID);
            uuid = [NSString stringWithString:(__bridge NSString *)string];
            CFRelease(string);
        }
        else
        {
            uuid = aID;
        }
        
        NSString *tempFileName = [NSString stringWithFormat:@"%@-%@",[aPath lastPathComponent],[NSString uuidStr]];
        NSString *tempPath = [[self.cachePath stringByAppendingPathComponent:tempFileName] stringByAppendingPathExtension:@"download"];
        
        MFWDownloadItem *managerItem = [[MFWDownloadItem alloc] init];
        managerItem.uuid = uuid;
        managerItem.url = aUrl;
        managerItem.tempPath = tempPath;
        managerItem.localPath = aPath;
        managerItem.state = MFWDownloadTaskStateNotInit;
        [self addTaskWithItem:managerItem];
        
        [self.db addItem:managerItem];
        index = self.itemAll.count - 1;
        [self startByIndex:index];
    }
    return index;
}

- (void)startWaitingItem
{
    if (self.itemWaiting.count > 0)
    {
        MFWDownloadTask *task = [self.itemWaiting objectAtIndex:0];
        [self startTask:task];
    }
}

- (void)MFWDownloadTaskFinished:(MFWDownloadTask*)aTask
{
    [self setTask:aTask state:MFWDownloadTaskStateFinished];
}

- (void)MFWDownloadTaskFailed:(MFWDownloadTask *)aTask
{
    [self setTask:aTask state:MFWDownloadTaskStateError];
}

- (NSArray*)getAllUUID
{
    NSMutableArray *arr = [[NSMutableArray alloc] init];
    for (MFWDownloadTask *task in self.itemAll)
    {
        [arr addObject:task.item.uuid];
    }
    return arr;
}

- (MFWDownloadTask*)getTaskByIndex:(NSUInteger)aIndex
{
    if (aIndex >= self.itemAll.count)
    {
        return nil;
    }
    return [self.itemAll objectAtIndex:aIndex];
}

- (NSUInteger)getCount
{
    return self.itemAll.count;
}

- (NSString*)getUUIDByIndex:(NSUInteger)aIndex
{
    MFWDownloadTask *task = [self getTaskByIndex:aIndex];
    return task.item.uuid;
}

- (NSUInteger)getIndexByUUID:(NSString *)aUUID
{
    MFWDownloadTask *task = [self.itemDict objectForKey:aUUID];
    if (!task)
    {
        return -1;
    }
    return [self.itemAll indexOfObject:task];
}

- (MFWDownloadProgress)getProgressByIndex:(NSUInteger)aIndex
{
    MFWDownloadTask *task = [self getTaskByIndex:aIndex];
    if (!task)
    {
        MFWDownloadProgress progress;
        progress.downloadLength = 0;
        progress.totalLength = 0;
        progress.percent = 0;
        return progress;
    }
    MFWDownloadProgress progress;
    unsigned long long max = -1;
    progress.downloadLength = task.downloadLength == max? 0: task.downloadLength;
    progress.totalLength = task.totalLength == max ? 0:task.totalLength;
    
    if (progress.totalLength == 0)
    {
        progress.percent = 0;
    }
    else
    {
        progress.percent = progress.downloadLength *1.0f / progress.totalLength;
    }
    
    if (progress.percent >1.01)
    {
        progress.percent = 0;
    }
    return progress;
}

// 停止
- (void)stopTask:(MFWDownloadTask*)aTask
{
    [self setTask:aTask state:MFWDownloadTaskStateStop];
}

- (void)stopByIndex:(NSUInteger)aIndex
{
    [self stopTask:[self getTaskByIndex:aIndex]];
}

- (void)stopAll
{
    for (MFWDownloadTask *task in self.itemAll)
    {
        [self stopTask:task];
    }
}

// 启动
- (void)startTask:(MFWDownloadTask*)aTask
{
    if ([self.itemRunning containsObject:aTask])
    {
        return;
    }
    MFWDownloadState state = self.itemRunning.count >= self.MaxConcurrentDownloadCount ?
MFWDownloadTaskStateWaiting:MFWDownloadTaskStateRunning;
    [self setTask:aTask state:state];
}

- (void)startByIndex:(NSUInteger)aIndex
{
    [self startTask:[self getTaskByIndex:aIndex]];
}

- (void)startAll
{
    for (MFWDownloadTask *task in self.itemAll)
    {
        [self startTask:task];
    }
}

// 删除
- (void)deleteByIndex:(NSUInteger)aIndex ifFinishedDeleteFile:(BOOL)aDelete
{
    MFWDownloadTask *task = [self getTaskByIndex:aIndex];
    if(!task)
    {
        return;
    }
    
    [self.db removeByID:task.item.uuid];
    
    [task clear];
    [self.itemStateGroups[task.item.state] removeObject:task];
    [self.itemDict removeObjectForKey:task.item.uuid];
    [self.itemAll removeObject:task];
    
    
    
    NSFileManager *manager = [NSFileManager defaultManager];
    if ([manager fileExistsAtPath:task.item.tempPath])
    {
        [manager removeItemAtPath:task.item.tempPath error:nil];
    }
    if (aDelete && [manager fileExistsAtPath:task.item.localPath])
    {
        [manager removeItemAtPath:task.item.localPath error:nil];
    }
}

// 暂停 挂起全部连接
- (void)pause
{
    for (MFWDownloadTask *task in self.itemRunning)
    {
        [self setTask:task state:MFWDownloadTaskStatePause];
    }
}

- (void)resume
{
    for (MFWDownloadTask *task in self.itemPause)
    {
        [self setTask:task state:MFWDownloadTaskStateRunning];
    }
}

- (NSString*)getUUIDByUrl:(NSString *)aUrl
{
    NSUInteger index = [self getIndexByUrl:aUrl];
    if (index < self.itemAll.count)
    {
        MFWDownloadTask *task = [self.itemAll objectAtIndex:index];
        return task.item.uuid;
    }
    return nil;
}

- (NSUInteger)getIndexByUrl:(NSString *)aUrl
{
    for (NSUInteger i = 0 ; i < self.itemAll.count ; i++)
    {
        MFWDownloadTask *task = [self.itemAll objectAtIndex:i];
        if ([task.item.url isEqualToString:aUrl])
        {
            return i;
        }
    }
    return -1;
}

@end
